﻿//-----------------------------------------------------------------------
// <copyright file="WebMessageSend.cs" company="AFS">
// Copyright (c) AFS. All rights reserved.
//
//    This file is part of Chatbox.
//
//    Chatbox is free software: you can redistribute it and/or modify
//    it under the terms of the GNU Lesser General Public License as published by
//    the Free Software Foundation, either version 3 of the License, or
//    any later version.
//
//    Chatbox is distributed in the hope that it will be useful,
//    but WITHOUT ANY WARRANTY; without even the implied warranty of
//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
//    GNU Lesser General Public License for more details.
//
//    You should have received a copy of the GNU Lesser General Public License
//    along with Chatbox. If not, see http://www.gnu.org/copyleft/lesser.html
// </copyright>
//----------------------------------------------------------------------- 

namespace ChatBox.Engine.Web
{
    using System;
    using System.Collections.Generic;
    using System.Net;
    using System.Threading;

    using ChatBox.DomainObjects;
    using ChatBox.DomainObjects.ResultData;
    using ChatBox.DomainObjects.Settings;
    using ChatBox.Interfaces;
    using ChatBox.Utility;

    /// <summary>
    /// Class that can send a message across the web
    /// </summary>
    public class WebMessageSend : IChatMessageSend
    {
        #region private

        /// <summary>
        /// Respository for serverSettings
        /// </summary>
        private readonly IServerSettingsRepository settingsRepository;

        /// <summary>
        /// Interface to the error log
        /// </summary>
        private readonly ILogger logger;

        /// <summary>
        /// Gets or sets the source for login details
        /// </summary>
        private readonly ILogOnSource logOnSource;

        /// <summary>
        /// The message, stored while waiting for a login
        /// </summary>
        private string messageStoredForLogon;

        /// <summary>
        /// The message that is being sent
        /// </summary>
        private string messageInProgress;

        /// <summary>
        /// the recipient of the message that is being sent
        /// </summary>
        private string recipientInProgress;

        /// <summary>
        /// The thread used
        /// </summary>
        private Thread thread;

        #endregion

        #region constructor

        /// <summary>
        /// Initializes a new instance of the WebMessageSend class 
        /// </summary>
        /// <param name="settingsRepository">The serverSettings repository</param>
        /// <param name="logger">The logger for errors</param>
        /// <param name="logOnSource">The source of logon details</param>
        public WebMessageSend(IServerSettingsRepository settingsRepository, ILogger logger, ILogOnSource logOnSource)
        {
            this.settingsRepository = settingsRepository;
            this.logger = logger;
            this.logOnSource = logOnSource;
            this.logOnSource.Completed += this.LogOnCompleted;
        }

        #endregion

        /// <summary>
        /// Event raided when sending has completed
        /// </summary>
        public event EventHandler<EventArgs<OperationResult>> Completed;

        /// <summary>
        /// Send the message to public chat
        /// </summary>
        /// <param name="message">the text of the message</param>
        public void SendMessage(string message)
        {
            this.recipientInProgress = string.Empty;
            this.SendMessageInternal(message);
        }

        /// <summary>
        /// Send a message to a particular user
        /// </summary>
        /// <param name="recipientName">the user name of the person to receive the message</param>
        /// <param name="message">the message text</param>
        public void SendMessageTo(string recipientName, string message)
        {
            this.recipientInProgress = recipientName;
            string packagedMessage = "/msg " + EncodeUserName(recipientName) + " " + message;
            this.SendMessageInternal(packagedMessage);
        }

        /// <summary>
        /// escape the user name for transport - it can't have spaces in it
        /// </summary>
        /// <param name="userName">the user name</param>
        /// <returns>the encoded user name</returns>
        private static string EncodeUserName(string userName)
        {
            return userName.Replace(" ", "_");
        }
        
        /// <summary>
        /// send the message to public chat or private user
        /// </summary>
        /// <param name="message">the text of the message</param>
        private void SendMessageInternal(string message)
        {
            if (this.logOnSource.LogOnCookie == null)
            {
                this.messageStoredForLogon = message;
                this.logOnSource.GetLogOn();
            }
            else
            {
                this.messageInProgress = message;
                this.SendMessageOnThread();
            }
        }

        /// <summary>
        /// Get the server settings 
        /// </summary>
        /// <returns>the server settings</returns>
        private ServerSettingsData GetServerSettings()
        {
            return this.settingsRepository.ReadSettings();
        }

        /// <summary>
        /// Notification that login has completed
        /// </summary>
        /// <param name="sender">the event sender</param>
        /// <param name="e">the event params</param>
        private void LogOnCompleted(object sender, EventArgs<LogOnResult> e)
        {
            if (this.logOnSource.LogOnCookie != null)
            {
                // log on suceeded. Is there a message to send?
                if (!string.IsNullOrEmpty(this.messageStoredForLogon))
                {
                    this.messageInProgress = this.messageStoredForLogon;
                    this.messageStoredForLogon = String.Empty;
                    this.SendMessageOnThread();
                }
            }
        }

        /// <summary>
        /// Send the stored message
        /// </summary>
        private void SendMessageOnThread()
        {
            this.thread = new Thread(this.ThreadMain);
            this.thread.IsBackground = true;
            this.thread.Name = "Chatbox message send thread";

            this.thread.Start();
        }

        /// <summary>
        /// The thread main 
        /// </summary>
        private void ThreadMain()
        {
            try
            {
                this.DoPost();
            }
            catch (Exception ex)
            {
                this.SendFailResult(ex);
            }
        }

        /// <summary>
        /// Send a failure result
        /// </summary>
        /// <param name="ex">the exception to send</param>
        private void SendFailResult(Exception ex)
        {
            if ((this.logger != null) && BaseWebDataSource.LogThisException(ex))
            {
                this.logger.LogException("Error sending message", ex);
            }

            // send the error back
            this.DoCompleted(new OperationResult(false, "Error sending message", ex));
        }

        /// <summary>
        /// Do the http post for the message
        /// </summary>
        private void DoPost()
        {
            string encodedMessage = this.messageInProgress.HtmlEncodeWithHighChars();

            ServerSettingsData serverSettings = this.GetServerSettings();
            Uri postUri = new Uri(serverSettings.Server + "/index.pl");

            Dictionary<string, string> formValues = new Dictionary<string, string>();
            formValues.Add("op", "message");
            formValues.Add("message", encodedMessage);

            // must have a logon cookie to post
            // get a local copy so other threads can't make me behave inconsitently
            Cookie localCookie = this.logOnSource.LogOnCookie;
            if (localCookie == null)
            {
                throw new ChatBoxException("No log-on cookie for post");
            }

            List<Cookie> cookies = new List<Cookie> { localCookie };

            using (HttpWebResponse response = WebHelpers.MultipartFormDataPost(postUri, EngineConstants.UserAgentString, formValues, cookies))
            {
                if (response != null)
                {
                    string sentMessage;
                    if (string.IsNullOrEmpty(this.recipientInProgress))
                    {
                        sentMessage = "Message sent";
                    }
                    else
                    {
                        sentMessage = "Message sent to " + this.recipientInProgress;
                    }

                    this.messageInProgress = string.Empty;
                    this.recipientInProgress = string.Empty;
                    this.DoCompleted(new OperationResult(true, sentMessage));
                }
            }
        }

        /// <summary>
        /// Fire the completed event
        /// </summary>
        /// <param name="result">the result values to send</param>
        private void DoCompleted(OperationResult result)
        {
            if (this.Completed != null)
            {
                this.Completed(this, new EventArgs<OperationResult>(result));
            }
        }
    }
}
